/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.sonos.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.joda.time.Period;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;
import org.openhab.binding.sonos.SonosCommandType;
import org.openhab.binding.sonos.internal.SonosBinding.SonosZonePlayerState;
import org.openhab.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.teleal.cling.UpnpService;
import org.teleal.cling.controlpoint.ActionCallback;
import org.teleal.cling.controlpoint.SubscriptionCallback;
import org.teleal.cling.model.action.ActionArgumentValue;
import org.teleal.cling.model.action.ActionException;
import org.teleal.cling.model.action.ActionInvocation;
import org.teleal.cling.model.gena.CancelReason;
import org.teleal.cling.model.gena.GENASubscription;
import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.meta.Action;
import org.teleal.cling.model.meta.RemoteDevice;
import org.teleal.cling.model.meta.Service;
import org.teleal.cling.model.meta.StateVariable;
import org.teleal.cling.model.meta.StateVariableTypeDetails;
import org.teleal.cling.model.state.StateVariableValue;
import org.teleal.cling.model.types.Datatype;
import org.teleal.cling.model.types.InvalidValueException;
import org.teleal.cling.model.types.UDAServiceId;
import org.teleal.cling.model.types.UDN;
import org.teleal.cling.model.types.UnsignedIntegerFourBytes;
import org.xml.sax.SAXException;
/**
* Internal data structure which carries the connection details of one Sonos player
* (there could be several)
*
* @author Karel Goderis
* @since 1.1.0
*
*/
class SonosZonePlayer {
private static Logger logger = LoggerFactory.getLogger(SonosZonePlayer.class);
protected final int interval = 600;
private boolean isConfigured = false;
/** the default socket timeout when requesting an url */
private static final int SO_TIMEOUT = 5000;
private RemoteDevice device = null;
private UDN udn;
private String id;
private DateTime lastOPMLQuery;
private SonosZonePlayerState savedState = null;
static protected UpnpService upnpService;
protected SonosBinding sonosBinding;
private Map<String, StateVariableValue> stateMap = Collections
.synchronizedMap(new HashMap<String, StateVariableValue>());
/**
* @return the stateMap
*/
public Map<String, StateVariableValue> getStateMap() {
return stateMap;
}
public boolean isConfigured() {
return isConfigured;
}
SonosZonePlayer(String id, SonosBinding binding) {
if (binding != null) {
this.id = id;
sonosBinding = binding;
}
}
private void enableGENASubscriptions() {
if (device != null && isConfigured()) {
// Create a GENA subscription of each service for this device, if supported by the device
List<SonosCommandType> subscriptionCommands = SonosCommandType.getSubscriptions();
List<String> addedSubscriptions = new ArrayList<String>();
for (SonosCommandType c : subscriptionCommands) {
Service service = device.findService(new UDAServiceId(c.getService()));
if (service != null && !addedSubscriptions.contains(c.getService())) {
SonosPlayerSubscriptionCallback callback = new SonosPlayerSubscriptionCallback(service, interval);
addedSubscriptions.add(c.getService());
upnpService.getControlPoint().execute(callback);
}
}
}
}
protected boolean isUpdatedValue(String valueName, StateVariableValue newValue) {
if (newValue != null && valueName != null) {
StateVariableValue oldValue = stateMap.get(valueName);
if (newValue.getValue() == null) {
// we will *not* store an empty value, thank you.
return false;
} else {
if (oldValue == null) {
// there was nothing stored before
return true;
} else {
if (oldValue.getValue() == null) {
// something was defined, but no value present
return true;
} else {
if (newValue.getValue().equals(oldValue.getValue())) {
return false;
} else {
return true;
}
}
}
}
}
return false;
}
protected void processStateVariableValue(String valueName, StateVariableValue newValue) {
if (newValue != null && isUpdatedValue(valueName, newValue)) {
Map<String, StateVariableValue> mapToProcess = new HashMap<String, StateVariableValue>();
mapToProcess.put(valueName, newValue);
stateMap.putAll(mapToProcess);
sonosBinding.processVariableMap(device, mapToProcess);
}
}
/**
* @return the device
*/
public RemoteDevice getDevice() {
return device;
}
/**
* @param device the device to set
*/
public void setDevice(RemoteDevice device) {
this.device = device;
if (upnpService != null && device != null) {
isConfigured = true;
enableGENASubscriptions();
}
}
public class SonosPlayerSubscriptionCallback extends SubscriptionCallback {
public SonosPlayerSubscriptionCallback(Service service) {
super(service);
}
public SonosPlayerSubscriptionCallback(Service service, int requestedDurationSeconds) {
super(service, requestedDurationSeconds);
}
@Override
public void established(GENASubscription sub) {
}
@Override
protected void failed(GENASubscription subscription, UpnpResponse responseStatus, Exception exception,
String defaultMsg) {
}
@Override
public void eventReceived(GENASubscription sub) {
// get the device linked to this service linked to this subscription
Map<String, StateVariableValue> values = sub.getCurrentValues();
Map<String, StateVariableValue> mapToProcess = new HashMap<String, StateVariableValue>();
Map<String, StateVariableValue> parsedValues = null;
// now, lets deal with the specials - some UPNP responses require some XML parsing
// or we need to update our internal data structure
// or are things we want to store for further reference
for (String stateVariable : values.keySet()) {
if (stateVariable.equals("LastChange") && service.getServiceType().getType().equals("AVTransport")) {
try {
parsedValues = SonosXMLParser.getAVTransportFromXML(values.get(stateVariable).toString());
for (String someValue : parsedValues.keySet()) {
// logger.debug("Lastchange parsed into {}:{}",someValue,parsedValues.get(someValue));
if (isUpdatedValue(someValue, parsedValues.get(someValue))) {
mapToProcess.put(someValue, parsedValues.get(someValue));
}
}
} catch (SAXException e) {
logger.error("Could not parse AVTransport from String {}",
values.get(stateVariable).toString());
}
} else if (stateVariable.equals("LastChange")
&& service.getServiceType().getType().equals("RenderingControl")) {
try {
parsedValues = SonosXMLParser.getRenderingControlFromXML(values.get(stateVariable).toString());
for (String someValue : parsedValues.keySet()) {
if (isUpdatedValue(someValue, parsedValues.get(someValue))) {
mapToProcess.put(someValue, parsedValues.get(someValue));
}
}
} catch (SAXException e) {
logger.error("Could not parse RenderingControl from String {}",
values.get(stateVariable).toString());
}
} else if (isUpdatedValue(stateVariable, values.get(stateVariable))) {
mapToProcess.put(stateVariable, values.get(stateVariable));
}
}
if (isConfigured) {
stateMap.putAll(mapToProcess);
sonosBinding.processVariableMap(device, mapToProcess);
}
}
@Override
public void eventsMissed(GENASubscription sub, int numberOfMissedEvents) {
logger.warn("Missed events: " + numberOfMissedEvents);
}
@Override
protected void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
if (device != null && isConfigured()) {
// rebooting the GENA subscription
Service service = subscription.getService();
SonosPlayerSubscriptionCallback callback = new SonosPlayerSubscriptionCallback(service, interval);
upnpService.getControlPoint().execute(callback);
}
}
}
public void setService(UpnpService service) {
if (upnpService == null) {
upnpService = service;
}
if (upnpService != null && device != null) {
isConfigured = true;
enableGENASubscriptions();
}
}
public String getModel() {
if (device != null) {
return device.getDetails().getModelDetails().getModelNumber();
} else {
return "Unknown";
}
}
/**
* @return the udn
*/
public UDN getUdn() {
return udn;
}
/**
* @param udn the udn to set
*/
public void setUdn(UDN udn) {
this.udn = udn;
}
public String getId() {
return id;
}
@Override
public String toString() {
return "Sonos [udn=" + udn + ", device=" + device + "]";
}
public boolean play() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("Play");
ActionInvocation invocation = new ActionInvocation(action);
invocation.setInput("Speed", "1");
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean playRadio(String station) {
if (isConfigured()) {
List<SonosEntry> stations = getFavoriteRadios();
SonosEntry theEntry = null;
// search for the appropriate radio based on its name (title)
for (SonosEntry someStation : stations) {
if (someStation.getTitle().equals(station)) {
theEntry = someStation;
break;
}
}
// set the URI of the group coordinator
if (theEntry != null) {
SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
coordinator.setCurrentURI(theEntry);
coordinator.play();
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* This will attempt to match the station string with a entry
* in the favorites list, this supports both single entries and playlists
*
* @param favorite to match
* @return true if a match was found and played.
*/
public boolean playFavorite(String station) {
if (isConfigured()) {
List<SonosEntry> stations = getFavorites();
SonosEntry theEntry = null;
// search for the appropriate favorite based on its name (title)
for (SonosEntry someStation : stations) {
if (someStation.getTitle().equals(station)) {
theEntry = someStation;
break;
}
}
// set the URI of the group coordinator
if (theEntry != null) {
SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
/**
* If this is a playlist we need to treat it as such
*/
if (theEntry.getResourceMetaData() != null
&& theEntry.getResourceMetaData().getUpnpClass().equals("object.container.playlistContainer")) {
coordinator.removeAllTracksFromQueue();
coordinator.addURIToQueue(theEntry);
coordinator.setCurrentURI("x-rincon-queue:" + udn.getIdentifierString() + "#0", "");
if (stateMap != null && isConfigured()) {
StateVariableValue firstTrackNumberEnqueued = stateMap.get("FirstTrackNumberEnqueued");
if (firstTrackNumberEnqueued != null) {
coordinator.seek("TRACK_NR", firstTrackNumberEnqueued.getValue().toString());
}
}
} else {
coordinator.setCurrentURI(theEntry);
}
coordinator.play();
return true;
} else {
return false;
}
} else {
return false;
}
}
public boolean playPlayList(String playlist) {
if (isConfigured()) {
List<SonosEntry> playlists = getPlayLists();
SonosEntry theEntry = null;
// search for the appropriate play list based on its name (title)
for (SonosEntry somePlaylist : playlists) {
if (somePlaylist.getTitle().equals(playlist)) {
theEntry = somePlaylist;
break;
}
}
// set the URI of the group coordinator
if (theEntry != null) {
SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
// coordinator.setCurrentURI(theEntry);
coordinator.addURIToQueue(theEntry);
if (stateMap != null && isConfigured()) {
StateVariableValue firstTrackNumberEnqueued = stateMap.get("FirstTrackNumberEnqueued");
if (firstTrackNumberEnqueued != null) {
coordinator.seek("TRACK_NR", firstTrackNumberEnqueued.getValue().toString());
}
}
coordinator.play();
return true;
} else {
return false;
}
} else {
return false;
}
}
public boolean stop() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("Stop");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean pause() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("Pause");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean next() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("Next");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean previous() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("Previous");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public String getZoneName() {
if (stateMap != null && isConfigured()) {
StateVariableValue value = stateMap.get("ZoneName");
if (value != null) {
return value.getValue().toString();
}
}
return null;
}
public String getZoneGroupID() {
if (stateMap != null && isConfigured()) {
StateVariableValue value = stateMap.get("LocalGroupUUID");
if (value != null) {
return value.getValue().toString();
}
}
return null;
}
public boolean isGroupCoordinator() {
if (stateMap != null && isConfigured()) {
StateVariableValue value = stateMap.get("GroupCoordinatorIsLocal");
if (value != null) {
return (Boolean) value.getValue();
}
}
return false;
}
public SonosZonePlayer getCoordinator() {
return sonosBinding.getCoordinatorForZonePlayer(this);
}
public boolean isCoordinator() {
return this.equals(getCoordinator());
}
public boolean addMember(SonosZonePlayer newMember) {
if (newMember != null && isConfigured()) {
SonosEntry entry = new SonosEntry("", "", "", "", "", "", "", "x-rincon:" + udn.getIdentifierString());
return newMember.setCurrentURI(entry);
} else {
return false;
}
}
public boolean removeMember(SonosZonePlayer oldMember) {
if (oldMember != null && isConfigured()) {
oldMember.becomeStandAlonePlayer();
SonosEntry entry = new SonosEntry("", "", "", "", "", "", "",
"x-rincon-queue:" + oldMember.getUdn().getIdentifierString() + "#0");
return oldMember.setCurrentURI(entry);
} else {
return false;
}
}
public boolean becomeStandAlonePlayer() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("BecomeCoordinatorOfStandaloneGroup");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean setMute(String string) {
if (string != null && isConfigured()) {
Service service = device.findService(new UDAServiceId("RenderingControl"));
Action action = service.getAction("SetMute");
ActionInvocation invocation = new ActionInvocation(action);
try {
invocation.setInput("Channel", "Master");
if (string.equals("ON") || string.equals("OPEN") || string.equals("UP")) {
invocation.setInput("DesiredMute", "True");
} else
if (string.equals("OFF") || string.equals("CLOSED") || string.equals("DOWN")) {
invocation.setInput("DesiredMute", "False");
} else {
return false;
}
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public String getMute() {
if (stateMap != null && isConfigured()) {
StateVariableValue value = stateMap.get("MuteMaster");
if (value != null) {
return value.getValue().toString();
}
}
return null;
}
public boolean setVolume(String value) {
if (value != null && isConfigured()) {
Service service = device.findService(new UDAServiceId("RenderingControl"));
Action action = service.getAction("SetVolume");
ActionInvocation invocation = new ActionInvocation(action);
try {
String newValue = value;
if (value.equals("INCREASE")) {
int i = Integer.valueOf(this.getVolume());
newValue = String.valueOf(Math.min(100, i + 1));
} else if (value.equals("DECREASE")) {
int i = Integer.valueOf(this.getVolume());
newValue = String.valueOf(Math.max(0, i - 1));
} else if (value.equals("ON")) {
newValue = "100";
} else if (value.equals("OFF")) {
newValue = "0";
} else {
newValue = value;
}
invocation.setInput("Channel", "Master");
invocation.setInput("DesiredVolume", newValue);
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public String getVolume() {
if (stateMap != null && isConfigured()) {
StateVariableValue value = stateMap.get("VolumeMaster");
if (value != null) {
return value.getValue().toString();
}
}
return null;
}
public boolean updateTime() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AlarmClock"));
Action action = service.getAction("GetTimeNow");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public String getTime() {
if (isConfigured()) {
updateTime();
if (stateMap != null) {
StateVariableValue value = stateMap.get("CurrentLocalTime");
if (value != null) {
return value.getValue().toString();
}
}
}
return null;
}
protected void executeActionInvocation(ActionInvocation invocation) {
if (invocation != null) {
new ActionCallback.Default(invocation, upnpService.getControlPoint()).run();
ActionException anException = invocation.getFailure();
if (anException != null && anException.getMessage() != null) {
logger.warn(anException.getMessage());
}
Map<String, ActionArgumentValue> result = invocation.getOutputMap();
Map<String, StateVariableValue> mapToProcess = new HashMap<String, StateVariableValue>();
if (result != null) {
// only process the variables that have changed value
for (String variable : result.keySet()) {
ActionArgumentValue newArgument = result.get(variable);
StateVariable newVariable = new StateVariable(variable,
new StateVariableTypeDetails(newArgument.getDatatype()));
StateVariableValue newValue = new StateVariableValue(newVariable, newArgument.getValue());
if (isUpdatedValue(variable, newValue)) {
mapToProcess.put(variable, newValue);
}
}
stateMap.putAll(mapToProcess);
sonosBinding.processVariableMap(device, mapToProcess);
}
}
}
public boolean updateRunningAlarmProperties() {
if (stateMap != null && isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("GetRunningAlarmProperties");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
// for this property we would like to "compile" a more friendly variable.
// this newly created "variable" is also store in the stateMap
StateVariableValue alarmID = stateMap.get("AlarmID");
StateVariableValue groupID = stateMap.get("GroupID");
StateVariableValue loggedStartTime = stateMap.get("LoggedStartTime");
String newStringValue = null;
if (alarmID != null && loggedStartTime != null) {
newStringValue = alarmID.getValue() + " - " + loggedStartTime.getValue();
} else {
newStringValue = "No running alarm";
}
StateVariable newVariable = new StateVariable("RunningAlarmProperties",
new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
StateVariableValue newValue = new StateVariableValue(newVariable, newStringValue);
processStateVariableValue(newVariable.getName(), newValue);
return true;
} else {
return false;
}
}
public String getRunningAlarmProperties() {
if (isConfigured()) {
updateRunningAlarmProperties();
if (stateMap != null) {
StateVariableValue value = stateMap.get("RunningAlarmProperties");
if (value != null) {
return value.getValue().toString();
}
}
}
return null;
}
public boolean updateZoneInfo() {
if (stateMap != null && isConfigured()) {
Service service = device.findService(new UDAServiceId("DeviceProperties"));
Action action = service.getAction("GetZoneInfo");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
Service anotherservice = device.findService(new UDAServiceId("DeviceProperties"));
Action anotheraction = service.getAction("GetZoneAttributes");
ActionInvocation anotherinvocation = new ActionInvocation(anotheraction);
executeActionInvocation(anotherinvocation);
// anotherservice = device.findService(new UDAServiceId("ZoneGroupTopology"));
// anotheraction = service.getAction("GetZoneGroupState");
// anotherinvocation = new ActionInvocation(anotheraction);
// executeActionInvocation(anotherinvocation);
return true;
} else {
return false;
}
}
public String getMACAddress() {
if (isConfigured()) {
updateZoneInfo();
if (stateMap != null) {
StateVariableValue value = stateMap.get("MACAddress");
if (value != null) {
return value.getValue().toString();
}
}
}
return null;
}
public boolean setLed(String string) {
if (string != null && isConfigured()) {
Service service = device.findService(new UDAServiceId("DeviceProperties"));
Action action = service.getAction("SetLEDState");
ActionInvocation invocation = new ActionInvocation(action);
try {
if (string.equals("ON") || string.equals("OPEN") || string.equals("UP")) {
invocation.setInput("DesiredLEDState", "On");
} else
if (string.equals("OFF") || string.equals("CLOSED") || string.equals("DOWN")) {
invocation.setInput("DesiredLEDState", "Off");
} else {
return false;
}
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean updateLed() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("DeviceProperties"));
Action action = service.getAction("GetLEDState");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean getLed() {
if (isConfigured()) {
updateLed();
if (stateMap != null) {
StateVariableValue variable = stateMap.get("CurrentLEDState");
if (variable != null) {
return variable.getValue().equals("On") ? true : false;
}
}
}
return false;
}
public String getCurrentZoneName() {
if (isConfigured()) {
updateCurrentZoneName();
if (stateMap != null) {
StateVariableValue variable = stateMap.get("CurrentZoneName");
if (variable != null) {
return variable.getValue().toString();
}
}
}
return null;
}
public boolean updateCurrentZoneName() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("DeviceProperties"));
Action action = service.getAction("GetZoneAttributes");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean updatePosition() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("GetPositionInfo");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public String getPosition() {
if (stateMap != null && isConfigured()) {
updatePosition();
if (stateMap != null) {
StateVariableValue variable = stateMap.get("RelTime");
if (variable != null) {
return variable.getValue().toString();
}
}
}
return null;
}
public boolean setPosition(String relTime) {
return seek("REL_TIME", relTime);
}
public boolean setPositionTrack(long tracknr) {
return seek("TRACK_NR", Long.toString(tracknr));
}
protected boolean seek(String unit, String target) {
if (isConfigured() && unit != null && target != null) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("Seek");
ActionInvocation invocation = new ActionInvocation(action);
try {
invocation.setInput("InstanceID", "0");
invocation.setInput("Unit", unit);
invocation.setInput("Target", target);
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
}
return false;
}
public Boolean isLineInConnected() {
if (stateMap != null && isConfigured()) {
StateVariableValue statusLineIn = stateMap.get("LineInConnected");
if (statusLineIn != null) {
return (Boolean) statusLineIn.getValue();
}
}
return null;
}
public Boolean isAlarmRunning() {
if (stateMap != null && isConfigured()) {
StateVariableValue status = stateMap.get("AlarmRunning");
if (status != null) {
return status.getValue().equals("1") ? true : false;
}
}
return null;
}
public String getTransportState() {
if (stateMap != null && isConfigured()) {
StateVariableValue status = stateMap.get("TransportState");
if (status != null) {
return status.getValue().toString();
}
}
return null;
}
public boolean addURIToQueue(String URI, String meta, int desiredFirstTrack, boolean enqueueAsNext) {
if (isConfigured() && URI != null && meta != null) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("AddURIToQueue");
ActionInvocation invocation = new ActionInvocation(action);
try {
invocation.setInput("InstanceID", "0");
invocation.setInput("EnqueuedURI", URI);
invocation.setInput("EnqueuedURIMetaData", meta);
invocation.setInput("DesiredFirstTrackNumberEnqueued", new UnsignedIntegerFourBytes(desiredFirstTrack));
invocation.setInput("EnqueueAsNext", enqueueAsNext);
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
}
return false;
}
public String getCurrentURI() {
updateMediaInfo();
if (stateMap != null && isConfigured()) {
StateVariableValue status = stateMap.get("CurrentURI");
if (status != null) {
return status.getValue().toString();
}
}
return null;
}
public long getCurrenTrackNr() {
if (stateMap != null && isConfigured()) {
updatePosition();
if (stateMap != null) {
StateVariableValue variable = stateMap.get("Track");
if (variable != null) {
return ((UnsignedIntegerFourBytes) variable.getValue()).getValue();
}
}
}
return -1;
}
public boolean updateMediaInfo() {
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("GetMediaInfo");
ActionInvocation invocation = new ActionInvocation(action);
try {
invocation.setInput("InstanceID", "0");
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
}
return false;
}
public boolean addURIToQueue(SonosEntry newEntry) {
return addURIToQueue(newEntry.getRes(), SonosXMLParser.compileMetadataString(newEntry), 1, true);
}
public boolean setCurrentURI(String URI, String URIMetaData) {
if (URI != null && URIMetaData != null && isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("SetAVTransportURI");
ActionInvocation invocation = new ActionInvocation(action);
try {
invocation.setInput("InstanceID", "0");
invocation.setInput("CurrentURI", URI);
invocation.setInput("CurrentURIMetaData", URIMetaData);
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean setCurrentURI(SonosEntry newEntry) {
return setCurrentURI(newEntry.getRes(), SonosXMLParser.compileMetadataString(newEntry));
}
public boolean updateCurrentURIFormatted() {
if (stateMap != null && isConfigured()) {
String currentURI = null;
SonosMetaData currentURIMetaData = null;
SonosMetaData currentTrack = null;
if (!isGroupCoordinator()) {
currentURI = getCoordinator().getCurrentURI();
currentURIMetaData = getCoordinator().getCurrentURIMetadata();
currentTrack = getCoordinator().getTrackMetadata();
} else {
currentURI = getCurrentURI();
currentURIMetaData = getCurrentURIMetadata();
currentTrack = getTrackMetadata();
}
if (currentURI != null) {
String resultString = null;
String artist = null;
String album = null;
String title = null;
if (currentURI.contains("x-sonosapi-stream")) {
// TODO: Get partner ID for openhab.org
String stationID = StringUtils.substringBetween(currentURI, ":s", "?sid");
StateVariable newVariable = new StateVariable("StationID",
new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
StateVariableValue newValue = new StateVariableValue(newVariable, stationID);
if (this.isUpdatedValue("StationID", newValue) || lastOPMLQuery == null
|| lastOPMLQuery.plusMinutes(1).isBeforeNow()) {
processStateVariableValue(newVariable.getName(), newValue);
String url = "http://opml.radiotime.com/Describe.ashx?c=nowplaying&id=" + stationID
+ "&partnerId=IAeIhU42&serial=" + getMACAddress();
String response = HttpUtil.executeUrl("GET", url, SO_TIMEOUT);
// logger.debug("RadioTime Response: {}",response);
lastOPMLQuery = DateTime.now();
List<String> fields = null;
try {
fields = SonosXMLParser.getRadioTimeFromXML(response);
} catch (SAXException e) {
logger.error("Could not parse RadioTime from String {}", response);
}
resultString = new String();
if (fields != null && fields.size() > 1) {
artist = fields.get(0);
title = fields.get(1);
Iterator<String> listIterator = fields.listIterator();
while (listIterator.hasNext()) {
String field = listIterator.next();
resultString = resultString + field;
if (listIterator.hasNext()) {
resultString = resultString + " - ";
}
}
}
} else {
resultString = stateMap.get("CurrentURIFormatted").getValue().toString();
title = stateMap.get("CurrentTitle").getValue().toString();
artist = stateMap.get("CurrentArtist").getValue().toString();
}
} else {
if (currentTrack != null) {
if (currentTrack.getResource().contains("x-rincon-stream")) {
title = currentTrack.getTitle();
album = " ";
artist = " ";
resultString = title;
} else if (!currentTrack.getResource().contains("x-sonosapi-stream")) {
if (currentTrack.getAlbumArtist().equals("")) {
resultString = currentTrack.getCreator() + " - " + currentTrack.getAlbum() + " - "
+ currentTrack.getTitle();
artist = currentTrack.getCreator();
} else {
resultString = currentTrack.getAlbumArtist() + " - " + currentTrack.getAlbum() + " - "
+ currentTrack.getTitle();
artist = currentTrack.getAlbumArtist();
}
album = currentTrack.getAlbum();
title = currentTrack.getTitle();
if (album.equals("")) {
album = " ";
}
if (artist.equals("")) {
artist = " ";
}
}
} else {
title = " ";
album = " ";
artist = " ";
resultString = " ";
}
}
StateVariable newVariable = new StateVariable("CurrentURIFormatted",
new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
StateVariableValue newValue = new StateVariableValue(newVariable, resultString);
processStateVariableValue(newVariable.getName(), newValue);
// update individual variables
newVariable = new StateVariable("CurrentArtist",
new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
if (artist != null) {
newValue = new StateVariableValue(newVariable, artist);
} else {
newValue = new StateVariableValue(newVariable, " ");
}
processStateVariableValue(newVariable.getName(), newValue);
newVariable = new StateVariable("CurrentTitle",
new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
if (title != null) {
newValue = new StateVariableValue(newVariable, title);
} else {
newValue = new StateVariableValue(newVariable, " ");
}
processStateVariableValue(newVariable.getName(), newValue);
newVariable = new StateVariable("CurrentAlbum",
new StateVariableTypeDetails(Datatype.Builtin.STRING.getDatatype()));
if (album != null) {
newValue = new StateVariableValue(newVariable, album);
} else {
newValue = new StateVariableValue(newVariable, " ");
}
processStateVariableValue(newVariable.getName(), newValue);
return true;
}
}
return false;
}
public String getCurrentURIFormatted() {
updateCurrentURIFormatted();
if (stateMap != null && isConfigured()) {
StateVariableValue status = stateMap.get("CurrentURIFormatted");
if (status != null) {
return status.getValue().toString();
}
}
return null;
}
public String getCurrentURIMetadataAsString() {
if (stateMap != null && isConfigured()) {
StateVariableValue value = stateMap.get("CurrentTrackMetaData");
if (value != null) {
return value.getValue().toString();
}
}
return null;
}
public SonosMetaData getCurrentURIMetadata() {
if (stateMap != null && isConfigured()) {
StateVariableValue value = stateMap.get("CurrentURIMetaData");
SonosMetaData currentTrack = null;
if (value != null) {
try {
if (((String) value.getValue()).length() != 0) {
currentTrack = SonosXMLParser.getMetaDataFromXML((String) value.getValue());
}
} catch (SAXException e) {
logger.error("Could not parse MetaData from String {}", value.getValue().toString());
}
return currentTrack;
} else {
return null;
}
} else {
return null;
}
}
public SonosMetaData getTrackMetadata() {
if (stateMap != null && isConfigured()) {
StateVariableValue value = stateMap.get("CurrentTrackMetaData");
SonosMetaData currentTrack = null;
if (value != null) {
try {
if (((String) value.getValue()).length() != 0) {
currentTrack = SonosXMLParser.getMetaDataFromXML((String) value.getValue());
}
} catch (SAXException e) {
logger.error("Could not parse MetaData from String {}", value.getValue().toString());
}
return currentTrack;
} else {
return null;
}
} else {
return null;
}
}
public SonosMetaData getEnqueuedTransportURIMetaData() {
if (stateMap != null && isConfigured()) {
StateVariableValue value = stateMap.get("EnqueuedTransportURIMetaData");
SonosMetaData currentTrack = null;
if (value != null) {
try {
if (((String) value.getValue()).length() != 0) {
currentTrack = SonosXMLParser.getMetaDataFromXML((String) value.getValue());
}
} catch (SAXException e) {
logger.error("Could not parse MetaData from String {}", value.getValue().toString());
}
return currentTrack;
} else {
return null;
}
} else {
return null;
}
}
public String getCurrentVolume() {
if (stateMap != null && isConfigured()) {
StateVariableValue status = stateMap.get("VolumeMaster");
return status.getValue().toString();
} else {
return null;
}
}
protected List<SonosEntry> getEntries(String type, String filter) {
List<SonosEntry> resultList = null;
if (isConfigured()) {
long startAt = 0;
Service service = device.findService(new UDAServiceId("ContentDirectory"));
Action action = service.getAction("Browse");
ActionInvocation invocation = new ActionInvocation(action);
try {
invocation.setInput("ObjectID", type);
invocation.setInput("BrowseFlag", "BrowseDirectChildren");
invocation.setInput("Filter", filter);
invocation.setInput("StartingIndex", new UnsignedIntegerFourBytes(startAt));
invocation.setInput("RequestedCount", new UnsignedIntegerFourBytes(200));
invocation.setInput("SortCriteria", "");
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
// Execute this action synchronously
new ActionCallback.Default(invocation, upnpService.getControlPoint()).run();
Long totalMatches = ((UnsignedIntegerFourBytes) invocation.getOutput("TotalMatches").getValue()).getValue();
Long initialNumberReturned = ((UnsignedIntegerFourBytes) invocation.getOutput("NumberReturned").getValue())
.getValue();
String initialResult = (String) invocation.getOutput("Result").getValue();
try {
resultList = SonosXMLParser.getEntriesFromString(initialResult);
} catch (SAXException e) {
logger.error("Could not parse Entries from String {}", initialResult);
}
startAt = startAt + initialNumberReturned;
while (startAt < totalMatches) {
invocation = new ActionInvocation(action);
try {
invocation.setInput("ObjectID", type);
invocation.setInput("BrowseFlag", "BrowseDirectChildren");
invocation.setInput("Filter", filter);
invocation.setInput("StartingIndex", new UnsignedIntegerFourBytes(startAt));
invocation.setInput("RequestedCount", new UnsignedIntegerFourBytes(200));
invocation.setInput("SortCriteria", "");
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
// Execute this action synchronously
new ActionCallback.Default(invocation, upnpService.getControlPoint()).run();
String result = (String) invocation.getOutput("Result").getValue();
int numberReturned = (Integer) invocation.getOutput("NumberReturned").getValue();
try {
resultList.addAll(SonosXMLParser.getEntriesFromString(result));
} catch (SAXException e) {
logger.error("Could not parse Entries from String {}", result);
}
startAt = startAt + numberReturned;
}
}
return resultList;
}
public List<SonosEntry> getArtists(String filter) {
return getEntries("A:", filter);
}
public List<SonosEntry> getArtists() {
return getEntries("A:", "dc:title,res,dc:creator,upnp:artist,upnp:album");
}
public List<SonosEntry> getAlbums(String filter) {
return getEntries("A:ALBUM", filter);
}
public List<SonosEntry> getAlbums() {
return getEntries("A:ALBUM", "dc:title,res,dc:creator,upnp:artist,upnp:album");
}
public List<SonosEntry> getTracks(String filter) {
return getEntries("A:TRACKS", filter);
}
public List<SonosEntry> getTracks() {
return getEntries("A:TRACKS", "dc:title,res,dc:creator,upnp:artist,upnp:album");
}
public List<SonosEntry> getQueue(String filter) {
return getEntries("Q:0", filter);
}
public List<SonosEntry> getQueue() {
return getEntries("Q:0", "dc:title,res,dc:creator,upnp:artist,upnp:album");
}
public List<SonosEntry> getPlayLists(String filter) {
return getEntries("SQ:", filter);
}
public List<SonosEntry> getPlayLists() {
return getEntries("SQ:", "dc:title,res,dc:creator,upnp:artist,upnp:album");
}
public List<SonosEntry> getFavoriteRadios(String filter) {
return getEntries("R:0/0", filter);
}
public List<SonosEntry> getFavoriteRadios() {
return getEntries("R:0/0", "dc:title,res,dc:creator,upnp:artist,upnp:album");
}
/**
* Searches for entries in the 'favorites' list on a sonos account
*
* @return
*/
public List<SonosEntry> getFavorites() {
return getEntries("FV:2", "dc:title,res,dc:creator,upnp:artist,upnp:album");
}
public List<SonosAlarm> getCurrentAlarmList() {
List<SonosAlarm> sonosAlarms = null;
if (isConfigured()) {
Service service = device.findService(new UDAServiceId("AlarmClock"));
Action action = service.getAction("ListAlarms");
ActionInvocation invocation = new ActionInvocation(action);
executeActionInvocation(invocation);
try {
sonosAlarms = SonosXMLParser
.getAlarmsFromStringResult(invocation.getOutput("CurrentAlarmList").toString());
} catch (SAXException e) {
logger.error("Could not parse Alarms from String {}",
invocation.getOutput("CurrentAlarmList").toString());
}
}
return sonosAlarms;
}
public boolean updateAlarm(SonosAlarm alarm) {
if (alarm != null && isConfigured()) {
Service service = device.findService(new UDAServiceId("AlarmClock"));
Action action = service.getAction("ListAlarms");
ActionInvocation invocation = new ActionInvocation(action);
DateTimeFormatter formatter = DateTimeFormat.forPattern("HH:mm:ss");
PeriodFormatter pFormatter = new PeriodFormatterBuilder().printZeroAlways().appendHours()
.appendSeparator(":").appendMinutes().appendSeparator(":").appendSeconds().toFormatter();
try {
invocation.setInput("ID", Integer.toString(alarm.getID()));
invocation.setInput("StartLocalTime", formatter.print(alarm.getStartTime()));
invocation.setInput("Duration", pFormatter.print(alarm.getDuration()));
invocation.setInput("Recurrence", alarm.getRecurrence());
invocation.setInput("RoomUUID", alarm.getRoomUUID());
invocation.setInput("ProgramURI", alarm.getProgramURI());
invocation.setInput("ProgramMetaData", alarm.getProgramMetaData());
invocation.setInput("PlayMode", alarm.getPlayMode());
invocation.setInput("Volume", Integer.toString(alarm.getVolume()));
if (alarm.getIncludeLinkedZones()) {
invocation.setInput("IncludeLinkedZones", "1");
} else {
invocation.setInput("IncludeLinkedZones", "0");
}
if (alarm.getEnabled()) {
invocation.setInput("Enabled", "1");
} else {
invocation.setInput("Enabled", "0");
}
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
public boolean setAlarm(String alarmSwitch) {
if (alarmSwitch.equals("ON") || alarmSwitch.equals("OPEN") || alarmSwitch.equals("UP")) {
return setAlarm(true);
} else
if (alarmSwitch.equals("OFF") || alarmSwitch.equals("CLOSED") || alarmSwitch.equals("DOWN")) {
return setAlarm(false);
} else {
return false;
}
}
public boolean setAlarm(boolean alarmSwitch) {
List<SonosAlarm> sonosAlarms = getCurrentAlarmList();
if (isConfigured()) {
// find the nearest alarm - take the current time from the Sonos System, not the system where openhab is
// running
String currentLocalTime = getTime();
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
DateTime currentDateTime = fmt.parseDateTime(currentLocalTime);
Duration shortestDuration = Period.days(10).toStandardDuration();
SonosAlarm firstAlarm = null;
for (SonosAlarm anAlarm : sonosAlarms) {
Duration duration = new Duration(currentDateTime, anAlarm.getStartTime());
if (anAlarm.getStartTime().isBefore(currentDateTime.plus(shortestDuration))
&& anAlarm.getRoomUUID().equals(udn.getIdentifierString())) {
shortestDuration = duration;
firstAlarm = anAlarm;
}
}
// Set the Alarm
if (firstAlarm != null) {
if (alarmSwitch) {
firstAlarm.setEnabled(true);
} else {
firstAlarm.setEnabled(false);
}
return updateAlarm(firstAlarm);
} else {
return false;
}
} else {
return false;
}
}
public boolean snoozeAlarm(int minutes) {
if (isAlarmRunning() && isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("SnoozeAlarm");
ActionInvocation invocation = new ActionInvocation(action);
Period snoozePeriod = Period.minutes(minutes);
PeriodFormatter pFormatter = new PeriodFormatterBuilder().printZeroAlways().appendHours()
.appendSeparator(":").appendMinutes().appendSeparator(":").appendSeconds().toFormatter();
try {
invocation.setInput("Duration", pFormatter.print(snoozePeriod));
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
} else {
logger.warn("There is no alarm running on {} ", this);
return false;
}
}
public boolean publicAddress() {
// check if sourcePlayer has a line-in connected
if (isLineInConnected() && isConfigured()) {
// first remove this player from its own group if any
becomeStandAlonePlayer();
List<SonosZoneGroup> currentSonosZoneGroups = new ArrayList<SonosZoneGroup>(
sonosBinding.getSonosZoneGroups().size());
for (SonosZoneGroup grp : sonosBinding.getSonosZoneGroups()) {
currentSonosZoneGroups.add((SonosZoneGroup) grp.clone());
}
// add all other players to this new group
for (SonosZoneGroup group : currentSonosZoneGroups) {
for (String player : group.getMembers()) {
SonosZonePlayer somePlayer = sonosBinding.getPlayerForID(player);
if (somePlayer != this) {
somePlayer.becomeStandAlonePlayer();
somePlayer.stop();
addMember(somePlayer);
}
}
}
// set the URI of the group to the line-in
// TODO : check if this needs to be set on the group coordinator or can be done on any member
SonosZonePlayer coordinator = getCoordinator();
SonosEntry entry = new SonosEntry("", "", "", "", "", "", "",
"x-rincon-stream:" + udn.getIdentifierString());
coordinator.setCurrentURI(entry);
coordinator.play();
return true;
} else {
logger.warn("Line-in of {} is not connected", this);
return false;
}
}
public boolean saveQueue(String name, String queueID) {
if (name != null && queueID != null && isConfigured()) {
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("SaveQueue");
ActionInvocation invocation = new ActionInvocation(action);
try {
invocation.setInput("Title", name);
invocation.setInput("ObjectID", queueID);
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
} else {
return false;
}
}
/**
* Play a given url to music in one of the music libraries.
*
* @param url in the format of //host/folder/filename.mp3
* @return true if the url started to play
*/
public boolean playURI(String url) {
if (!isConfigured) {
return false;
}
SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
// stop whatever is currently playing
coordinator.stop();
// clear any tracks which are pending in the queue
coordinator.removeAllTracksFromQueue();
// add the new track we want to play to the queue
if (!url.startsWith("x-")) {
// default to file based url
url = "x-file-cifs:" + url;
}
coordinator.addURIToQueue(url, "", 0, true);
// set the current playlist to our new queue
coordinator.setCurrentURI("x-rincon-queue:" + udn.getIdentifierString() + "#0", "");
// take the system off mute
coordinator.setMute("OFF");
// start jammin'
return coordinator.play();
}
/**
* Play music from the line-in of the Player given its name or UDN
*
* @param udn
* @return true if the sonos device started to play
*/
public boolean playLineIn(String remotePlayerName) {
if (!isConfigured) {
return false;
}
SonosZonePlayer coordinator = sonosBinding.getCoordinatorForZonePlayer(this);
SonosZonePlayer remotePlayer = sonosBinding.getPlayerForID(remotePlayerName);
// stop whatever is currently playing
coordinator.stop();
// set the
coordinator.setCurrentURI("x-rincon-stream:" + remotePlayer.getUdn().getIdentifierString(), "");
// take the system off mute
coordinator.setMute("OFF");
// start jammin'
return coordinator.play();
}
/**
* Clear all scheduled music from the current queue.
*
* @return true if no error occurred.
*/
public boolean removeAllTracksFromQueue() {
if (!isConfigured) {
return false;
}
Service service = device.findService(new UDAServiceId("AVTransport"));
Action action = service.getAction("RemoveAllTracksFromQueue");
ActionInvocation invocation = new ActionInvocation(action);
try {
invocation.setInput("InstanceID", "0");
} catch (InvalidValueException ex) {
logger.error("Action Invalid Value Exception {}", ex.getMessage());
} catch (NumberFormatException ex) {
logger.error("Action Invalid Value Format Exception {}", ex.getMessage());
}
executeActionInvocation(invocation);
return true;
}
/**
* Save the state (track, position etc) of the Sonos Zone player.
*
* @return true if no error occurred.
*/
protected boolean saveState() {
synchronized (this) {
savedState = sonosBinding.new SonosZonePlayerState();
String currentURI = getCurrentURI();
if (currentURI != null) {
if (currentURI.contains("x-sonosapi-stream:")) {
// we are streaming music
SonosMetaData track = getTrackMetadata();
SonosMetaData current = getCurrentURIMetadata();
if (track != null) {
savedState.entry = new SonosEntry("", current.getTitle(), "", "", track.getAlbumArtUri(), "",
current.getUpnpClass(), currentURI);
}
} else if (currentURI.contains("x-rincon:")) {
// we are a slave to some coordinator
savedState.entry = new SonosEntry("", "", "", "", "", "", "", currentURI);
} else if (currentURI.contains("x-rincon-stream:")) {
// we are streaming from the Line In connection
savedState.entry = new SonosEntry("", "", "", "", "", "", "", currentURI);
} else if (currentURI.contains("x-rincon-queue:")) {
// we are playing something that sits in the queue
SonosMetaData queued = getEnqueuedTransportURIMetaData();
if (queued != null) {
savedState.track = getCurrenTrackNr();
if (queued.getUpnpClass().contains("object.container.playlistContainer")) {
// we are playing a real 'saved' playlist
List<SonosEntry> playLists = getPlayLists();
for (SonosEntry someList : playLists) {
if (someList.getTitle().equals(queued.getTitle())) {
savedState.entry = new SonosEntry(someList.getId(), someList.getTitle(),
someList.getParentId(), "", "", "", someList.getUpnpClass(),
someList.getRes());
break;
}
}
} else if (queued.getUpnpClass().contains("object.container")) {
// we are playing some other sort of
// 'container' - we will save that to a
// playlist for our convenience
logger.debug("Save State for a container of type {}", queued.getUpnpClass());
// save the playlist
String existingList = "";
List<SonosEntry> playLists = getPlayLists();
for (SonosEntry someList : playLists) {
if (someList.getTitle().equals("openHAB-" + getUdn())) {
existingList = someList.getId();
break;
}
}
saveQueue("openHAB-" + getUdn(), existingList);
// get all the playlists and a ref to our
// saved list
playLists = getPlayLists();
for (SonosEntry someList : playLists) {
if (someList.getTitle().equals("openHAB-" + getUdn())) {
savedState.entry = new SonosEntry(someList.getId(), someList.getTitle(),
someList.getParentId(), "", "", "", someList.getUpnpClass(),
someList.getRes());
break;
}
}
}
} else {
savedState.entry = new SonosEntry("", "", "", "", "", "", "",
"x-rincon-queue:" + getUdn().getIdentifierString() + "#0");
}
}
savedState.transportState = getTransportState();
savedState.volume = getCurrentVolume();
savedState.relTime = getPosition();
} else {
savedState.entry = null;
}
return true;
}
}
/**
* Restore the state (track, position etc) of the Sonos Zone player.
*
* @return true if no error occurred.
*/
protected boolean restoreState() {
synchronized (this) {
if (savedState != null) {
// put settings back
setVolume(savedState.volume);
if (isCoordinator()) {
if (savedState.entry != null) {
// check if we have a playlist to deal with
if (savedState.entry.getUpnpClass().contains("object.container.playlistContainer")) {
addURIToQueue(savedState.entry.getRes(),
SonosXMLParser.compileMetadataString(savedState.entry), 0, true);
SonosEntry entry = new SonosEntry("", "", "", "", "", "", "",
"x-rincon-queue:" + getUdn().getIdentifierString() + "#0");
setCurrentURI(entry);
setPositionTrack(savedState.track);
} else {
setCurrentURI(savedState.entry);
setPosition(savedState.relTime);
}
if (savedState.transportState.equals("PLAYING")) {
play();
} else if (savedState.transportState.equals("STOPPED")) {
stop();
} else if (savedState.transportState.equals("PAUSED_PLAYBACK")) {
pause();
}
}
}
}
return true;
}
}
}